Introduction to Python

part 13 OpenCV

2021/6/22

參考資料

安裝

conda install -c conda-forge opencv

引入套件

import cv2

圖片的大小計算與黑白、灰階

讀取圖檔、顯示、輸出

視窗控制

用opencv讀取、用matplitlib顯示

透過 matplotlib 直接在 jupyter 中直接顯示圖片,除了開發快速(能直接顯示結果)之外,也能快速查找座標。
要注意一般的顏色通道通常為 RGB,但 OpenCV 中處理圖片的顏色通道為 BGR

彩色圖:

Matplotlib顯示圖片只要呼叫imshow就可以了,但是由於OpenCV讀取進來的圖片會以BGR的方式儲存三個顏色的channel,

如果直接把OpenCV讀入的圖片放進Matplotlib顯示,會出現顏色錯誤問題,必須要先將作轉換BGR $\rightarrow$ RGB

灰階圖:

以OpenCV讀取灰階的圖片時,由於channel只有一個,所以不會有色彩問題,直接把OpenCV讀入的NumPy陣列放進Matplotlib的imshow中即可顯示, 但是 Matplotlib 在顯示一個channel的圖片時,會用預設的colormap上色,如果想要以黑白的方式呈現灰階圖片,要設定colormap為gray

RGB三色以灰階顯示

實際上,RGB圖像是由三個通道疊加而成的:R, G, B,所以把每個通道一個一個的描繪出來,就可以理解顏色通道是如何構成的了。

觀察下面的圖片,在R通道圖中,紅色飽和度高的部分看起來是白色的,這是由於紅色部分中的值接近255。

在灰度模式下,值越高顏色就越白。還可以使用G或B通道來檢查這一點,並比較某些部分之間的差異

調整亮度

運用array的操作,可以對圖作亮度調整

如果是增加光線:數值往 255靠近 (更亮) 如果是減少光線:數值往 0靠近 (更暗)

範例:

公式化簡後,除了 phi、theta 能夠改變最終值外, 是讓原本的 origin 值開根號乘上根號255,所以會呈現凹口向下的曲線。

HSV與 HLS

  1. 正如在上圖看到的那樣,三維的表達,更類似於人類的感知方式。

HSV的中軸是色值,HSL的中軸是光量。沿著中心軸的角度,有色調和實際的顏色。與中心軸的距離屬於飽和度。

  1. 將int範圍(0 ~ 255)的顏色表示轉換為浮點數(0 ~ 1),這樣方便做比例上的運算。
    把最後的結果會寫為 1 + x/100.0,改變的lightness、saturation值都是對應x改變的% (正或負) 大小。
    避免運算結果超過範圍,我們在運算後,將所有超過1的值皆設為1。

影像裁切

只需要知道座標後,決定要裁減的範圍即可。

改變圖片比例

https://shengyu7697.github.io/python-opencv-resize/

cv2.resize(input_image_array, (width, height) , interpolation=cv2.INTER_AREA)

interpolation的選項:

如果是要縮小圖片的話,通常 INTER_AREA 使用效果較佳。 如果是要放大圖片的話,通常 INTER_CUBIC 使用效果較佳,次等則是 INTER_LINEAR。 如果要追求速度的話,通常使用 INTER_NEAREST。

調整色調(冷色系/暖色系)、色溫(白平衡)modify color temperature, white balance

冷、暖色調,其實就是色溫變化的結果,而色溫的變化,通常也就伴隨著「白平衡的破壞」。
一個白平衡的圖片,色調並不會明顯偏冷或偏暖,但因為人類視覺上的觀感,有時我們能透過色調使圖片更有特色。

例如:

增加顆粒感 (增加高斯噪點) add gaussian noise

高斯噪聲大概長這樣(平均=0,標準差 = 0.1): (請注意:雖然是常態分佈,但值依然是隨機的,所以每次產生的結果都會不同!) 其實就是把噪聲到圖上加起來而已哈哈哈哈。 上面已經有標準差 = 0.1的結果, 我們下面換一個比較大一點的標準差 = 0.5的結果,我們看看有什麼樣的效果~~

改變圖片的對比度 modify contrast

問題

上面的方法是乘上某一個alpha值,使分佈被放大,再進行縮小,這樣能讓越大的數字(接近255),被放的更大(超過255後,被計算為255), 也就是說「白的更白」實現了。
但「黑的更黑」呢? 上面公式就是出現了這樣的問題,越接近0的值,透過上面的公式沒有辦法更接近0, 甚至反而因為放大效果,離0更遠了。這樣就不太符合增加對比度的意義了,

修改版

運用三角函數的特性,

$ c = \dfrac{contrast}{255.0} $,contrast = (-255, +255),代表 c = (-1, +1)

$k = \tan \dfrac{45 + 44 * c}{180\pi}$,將 $c$ 代入,得到約 $\tan\dfrac{(1:89)}{180\pi}$,

tan的特性:

img = (img - 127.5 * (1 - B)) * k + 127.5 * (1 + B)

255的一半為127.5,後面127.5是正的,前面127.5是負的,因為乘上k,k可以決定要更大的負(整個式子結果更負)或更小的負(整個式子結果更正)。
因為我們是從中間 127.5 開始切,所以就會有小於127.5 往更負的方向跑,大於127.5 往更正的方向跑的現象。
但隨著值越大,一定會超出 0 ~ 255 的顏色範圍,所以最後我們用:

img = np.clip(img, 0, 255).astype(np.uint8)

所有值必須介於 0~255 之間,超過255 = 255,小於 0 = 0

顯示圖片直方圖、分離與合併RGB通道 show histogram, split, merge RGB channel

旋轉、平移、鏡射與透射

https://www.jianshu.com/p/ef67cacf442c

翻轉

cv2.flip(src, flipCode)

平移

warpAffine(InputArray src, OutputArray dst, InputArray M, Size dsize, int flags=INTER_LINEAR, int borderMode=BORDER_CONSTANT, const Scalar& borderValue=Scalar())

旋轉指定角度

仿射

透視 Perspective

M = cv2.getPerspectiveTransform(pos1,pos2)

cv2.warpPerspective(src, M, (widths, heights))

建立全黑、全白空白圖

先定義好要的圖片大小。如果要彩色的圖片,維度需要設3 (才有RGB通道), 照我們設定的圖片大小,將全部數值定為0,形成全黑圖;把數值全定為255,形成全白圖。

建立全黑的新圖片 100*100

shape = (100, 100, 3) # y, x, RGB origin_img = np.zeros(shape, np.uint8)

建立全白的新圖片 100*100

shape = (100, 100, 3) # y, x, RGB

#第一種方法,直接建立全白圖片 100*100 origin_img = np.full(shape, 255).astype(np.uint8)

#第二種方法,一樣先建立全黑的圖片,再將全部用白色填滿。 origin_img = np.zeros(shape, np.uint8) origin_img.fill(255)

在圖像內畫圖

直線

cv2.line(src, pt1, pt2, color, thickness, lineType)

圓形

cv2.circle(src, center, radius, color, thickness, lineType)

矩形

cv2.rectangle(src, left_up, right_down, color, thickness, lineType)

橢圓

cv2.ellipse(img, center, axes, rotateangle, startAngle, endAngle, color, thickness)

 多邊形

cv2.polylines(img, pts, isClosed, color, thickness, lineType)

加文字

cv2.putText(img, text, org, fontFace, fontScale, color, thickness, lineType)

查詢色碼

(B, G, R) = cv2.split(img) # 3 channel b, g, r = B[y ,x], G[y ,x], R[y ,x] print("RGB = ({}, {}, {})".format(r, g, b))

b, g, r = img[y, x] print("RGB = ({}, {}, {})".format(r, g, b))

圖層的功能:加減法 Add, Subtract, AddWeighted

遮罩(mask): 假設將全部的值都設為128,也就是灰色的一張圖片

$$ dst = \alpha \cdot img1 + \beta \cdot img2 + \gamma $$

幾乎只會使用這個來修圖,這個就是真正遮罩的概念了。

overlapping = cv2.addWeighted(img, 0.8, mask, 0.2, 0)

https://docs.opencv.org/master/d0/d86/tutorial_py_image_arithmetics.html

圖片的輪廓(高斯模糊、Canny) cv2.GaussianBlur, cv2.Canny

1.因為彩色的圖片有三個顏色通道需要處理,但若只需找到輪廓,用一個通道即可,這樣使得在運算上不會到太複雜,因此會先將圖片轉成灰階。

2.高斯模糊(高斯平滑):將圖片模糊化

cv2.GaussianBlur(gray,(kernel_size, kernel_size), 0)

kernel_size 是進行運算時,對於多大範圍的圖片進行運算。
簡單可以理解為:如果對 5x5 的 kernel_size 運算,那圖片每一個 5x5 的中心受到高斯模糊的影響會最大,算出來就會是一張比較模糊的圖片。

Q: 為什麼要模糊?
A: 因為先做高斯模糊(或說經過高斯平滑)的圖片能夠去除很多圖片的噪聲(雜訊),更容易讓我們找到更精準的輪廓。

3.Canny 邊緣檢測

edges = cv2.Canny(blur_gray, low_threshold, high_threshold)

其中有兩個參數需要自己依情況調整,我們知道灰階圖片的值介於 0~255 之間,情況有三種:

影像平滑模糊化 blur

https://shengyu7697.github.io/python-opencv-blur/

https://www.jianshu.com/p/4ae5e8cef9ae

影像平滑模糊化是透過使用低通濾波器進行影像卷積來實現的,對於消除雜訊很有用。 實際上使用此濾波器時,它會從影像中去除高頻內容(例如,雜訊,邊緣),也會導致影像邊緣變得模糊(也有其他濾波器不會造成影像邊緣模糊)。

OpenCV主要提供四種類型的平滑模糊化技術。

圖片聯集bitwise_or、交集bitwise_and、互斥bitwise_xor

分別作一白色、藍色圓

P圖的操作

影像二值化與閾值thresholding

https://www.jianshu.com/p/3a94020bc1ac

https://shengyu7697.github.io/python-opencv-threshold/

影像二值化的方法:

  1. Simple Thresholding

如果像素值pixel大於閾值(門檻值)threshold,就指定一個新數值(例如白色),否則就指定另一個新數值(例如黑色)。 cv2.threshold 最主要的功能是能夠幫助我們將一張圖片做二值化,二值化的意思是圖片只會剩下兩個值,通常是黑(255)與白(0)。

為什麼我們需要做二值化呢?
圖片二值化後,對於整張圖片來說就是乾淨的兩個值,只有乾淨的兩個值,很適合我們做一些輪廓偵測的運算。
而算出輪廓後我們就可以做很多圖形的處理,例如畫出圖片輪廓、圖片邊緣的銳利度強化、加粗圖片的輪廓線、依照輪廓線替圖片添加陰影...
搭配其他功能的操作能實現非常多的效果!

ret, dst = cv2.threshold(src, thresh, maxval, type)

編號 閾值
1 THRESH_BINARY
2 THRESH_BINARY_INV
3 THRESH_TRUNC
4 THRESH_TOZERO
5 THRESH_TOZERO_INV
6 THRESH_MASK
7 THRESH_OTSU
8 THRESH_TRIANGLE

參考這張圖 https://docs.opencv.org/2.4/_images/threshold.png

以下範例使用參數:

2.自適應二值化,產生更好效果的黑白圖片 cv2.adaptiveThreshold

一般的「二值化」只會考慮單一點的值,直接去做閥值分析, 但一張圖片的「每個鄰近的像素都是彼此有關連的」,如果單純只針對單一個點去看,似乎失去了對整張圖相鄰點的考慮, 因此「自適應二值化」就是在幫助我們找到單一點與鄰近區域的關係。

自適應的二值化又分為

cv2.adaptiveThreshold(image, 255, 自適應二值化算法, 閥值類型, 參考局部大小, 偏移量)

自適應二值化,搭配模糊降噪,能有更好的效果。
通常在做「自適應二值化」之前,我們都會先將圖片做模糊化,能夠達到降噪的效果,看下圖結果的圖片應該能很明顯地分得出差別。

Otsu's Threshold 大津二值化,自動計算最佳閥值,做出最好的黑白效果圖

漫水填充法 cv2.floodFill (Magic Wand Tool)

P圖,合併merge two images

步驟:

  1. 將要P的圖,改成適當大小 (cv2.resize)
  2. 將要P的圖,使用漫水填充法,濾出背景 (cv2.floodFill)
  3. 將濾出背景的圖,挖出人物,取得遮罩(黑色) (cv2.cvtColor, cv2.threshold)
  4. 將遮罩印在原來的圖片上,有點像挖空的感覺 (cv2.bitwise_and)
  5. 挖空後,反向取得原先要P圖的人物 (cv2.bitwise_not, cv2.bitwise_and)
  6. 合併人物至原來的圖片 (cv2.add)

人臉、人眼偵測

https://ithelp.ithome.com.tw/articles/10251665

  1. 特徵分類器為一個XML檔,安裝OpenCV時同時放入,要找到路徑,或是複製到指定路徑PATH
  1. 偵測圖片
  1. 框出人臉與眼:

for (x, y, w, h) in faces:

 cv2.rectangle(img, (x, y), (x + w, y + h), color=(0, 255, 0), thickness=2) #畫矩形 

 for (x2,y2,w2,h2) in eyes:
            eye_center = (x + x2 + w2//2, y + y2 + h2//2)  
            radius = int(round((w2 + h2)*0.25))
            cv2.circle(img, eye_center, radius, (255, 0, 0 ), 4) #畫圓

影片